Skip to content

Conversation

tompng
Copy link
Member

@tompng tompng commented Sep 18, 2025

With #407, exp(x, N) and sin(x, N) calculation improves from O(N^3/logN) to O(N^2).
This pull request further improves O(N^2) to O(N*(logN)**a) where a is about 3 or 4.

BigMath.exp(1, 10_000_000) # 13.146789s
BigMath.exp(BigDecimal(2).sqrt(10_000_000), 10_000_000) # 167.920334s

Drops Ruby 2.5 support to use (1..).bsearch

Binary Splitting method

If x can be represented as small_numerator/small_denominator, exp(x) and sin(x) can be calculated fast using binary splitting method.
This technique is normally used in PI calculation.
Here's an example of calculating exp(1).

exp(1) = 1 + 1/1 * (1 + 1/2 * (1 + 1/3 * (1 + 1/4 * (1 + 1/5 * (1 + 1/6 * (1 + 1/7 * (1 + 1/8 * (1 + rest))))))))
       = 2 + 1/2 * (4/3 + 1/12 * (6/5 + 1/30 * (8/7 + 1/56 * (1 + rest))))
       = 8/3 + 1/24 * (26/21 + 1/1680 * (1 + rest))
       = 109601/40320 + 1/40320 * rest

The prerequisite is that all numbers ​​are small in the initial step.
In each step, average number of digits doubles and number of terms halves.

Split digits (decimal form of bit-burst algorithm)

Normally, x has many digits, so simply applying binary splitting method to exp(x) isn't fast.
Now lets split x into several parts.

exp(x.xxxxxxxxxxxxxxxx) = exp(x.xx) * exp(0.00xx) * exp(0.0000xxxx) * exp(0.00000000xxxxxxxx)

Number of digits doubled, number of terms required for convergence is almost halved.

exp(0.00xx, 1000)                 # digits: 2,  terms: 253
exp(0.0000xxxx, 1000)             # digits: 4,  terms: 173
exp(0.00000000xxxxxxxx, 1000)     # digits: 8,  terms: 105
exp(0.0000000000000000x..., 1000) # digits: 16, terms: 58

So each exp calculation performs well with binary splitting method.
We need to repeat binary splitting method for about logN times, so the total time complexity will be quasilinear time.

BigMath.sin

Almost the same as BigMath.exp.
Restore sin(x.xxxxxxxxxxxxxxxx) from sin(x.xx), sin(0.00xx), sin(0.0000xxxx), sin(0.00000000xxxxxxxx) using trigonometric addition theorem.

BigMath.log and BigMath.atan

Since exp and sin becomes orders of magnitude faster, implementing log and atan by newton's method is better.
log(x) : Solve exp(y) - x = 0
atan(x) : Solve tan(y) - x = 0
There is an option to implement binary splitting method to log and atan taylor series, but implementation will be more complicated, needs special care to make |x| ≪ 1, and convergence is slower than exp and sin.

@tompng tompng force-pushed the faster_exp_sin_log_atan branch 6 times, most recently from 91c8357 to 47bae84 Compare September 22, 2025 17:31
tompng added 2 commits October 7, 2025 22:03
…method

exp and sin becomes orders of magnitude faster.
To make log and atan also fast, log and atan now depends on exp and sin.
log(x): solve exp(y)-x=0 by Newton's method
atan(x): solve tan(y)-x=0 by Newton's method
bsearch for endless range is only available in ruby >= 2.6
@tompng tompng force-pushed the faster_exp_sin_log_atan branch from 47bae84 to ae45a22 Compare October 7, 2025 13:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant